﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace ZHello.Utils
{

    /*
    把不在程序目录下的引用文件，复制到存储目录，在程序目录的引用文件，仅记录相对路径和文件名
    记录文件：用于记录所有的存储的文件信息，路径：/存储目录/res/.resfile(隐藏文件)
    被复制文件：路径：/存储目录/res/<SHA>/<被复制文件名>

    1.通过SHA区分文件的不同，并非文件名和路径
    2.可以对于某个目录下的文件，不进行复制到存储目录
    3.
     
     */
    /// <summary>
    /// 
    /// </summary>
    public class FileSaveUtil
    {
        /// <summary>
        /// 计算文件的 SHA256 值
        /// </summary>
        /// <param name="fileStream">文件流</param>
        /// <returns>System.String.</returns>
        public static string SHA256File(FileStream fileStream)
        {
            SHA256 mySHA256 = SHA256.Create();
            byte[] hashValue;
            // Create a fileStream for the file.
            //FileStream fileStream = fInfo.Open(FileMode.Open);
            // Be sure it's positioned to the beginning of the stream.
            fileStream.Position = 0;
            // Compute the hash of the fileStream.
            hashValue = mySHA256.ComputeHash(fileStream);
            // Close the file.
            fileStream.Close();
            // Write the hash value to the Console.
            StringBuilder sb = new StringBuilder();
            int i;
            for (i = 0; i < hashValue.Length; i++)
            {
                sb.Append(string.Format("{0:X2}", hashValue[i]));
            }
            return sb.ToString();
        }

        public static bool XmlSerialize<T>(T t, out string xmlStr)
        {
            bool result = false;
            xmlStr = null;
            try
            {
                var ns = new XmlSerializerNamespaces();
                ns.Add("", "");
                var ser = new XmlSerializer(typeof(T));
                using (MemoryStream ms = new MemoryStream())
                {
                    ser.Serialize(ms, t, ns);
                    ms.Position = 0;
                    StreamReader reader = new StreamReader(ms);
                    xmlStr = reader.ReadToEnd();
                    reader.Close();
                    ms.Close();
                    if (!string.IsNullOrEmpty(xmlStr))
                    {
                        result = true;
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
                Trace.TraceError(ex.StackTrace);
            }
            return result;
        }

        public static bool XmlDeserialize<T>(string xmlStr, out T t)
        {
            bool result = false;
            t = default(T);
            if (string.IsNullOrEmpty(xmlStr))
            {
                return false;
            }
            try
            {
                using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xmlStr)))
                {
                    var ser = new XmlSerializer(typeof(T));
                    t = (T)ser.Deserialize(ms);
                    result = true;
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
                Trace.TraceError(ex.StackTrace);
            }
            return result;
        }

        public const string F_file = ".resfile";
        private static FileList Lis;
        private static string LoadDir;
        private static string RootDir;

        public static void Save(string dir)
        {
            ProcLis();
            var fn = Path.Combine(dir, F_file);
            if (File.Exists(fn))
                File.Delete(fn);
            using (var writer = new StreamWriter(File.OpenWrite(fn)))
            {
                string xmlStr;
                if (XmlSerialize(Lis, out xmlStr))
                {
                    writer.Write(xmlStr);
                }
            }
            var finfo = new FileInfo(fn);
            finfo.Attributes = FileAttributes.Hidden;
            finfo.Refresh();
        }

        public static void SetRootDir(string root)
        {
            RootDir = root;
        }

        public static void Load(string dir)
        {
            if (LoadDir == dir)
                return;
            LoadDir = dir;
            var fn = Path.Combine(dir, F_file);
            if (!File.Exists(fn))
            {
                Lis = new FileList();
                return;
            }
            using (var writer = new StreamReader(File.OpenRead(fn)))
            {
                string xmlStr = writer.ReadToEnd();
                XmlDeserialize(xmlStr, out Lis);
            }
        }

        public static void ProcLis()
        {
            if (Lis == null)
                return;
            foreach (var item in Lis.Items)
            {
                GenerateUID(item);
            }
            new DirectoryInfo(LoadDir)
                .GetDirectories()
                .ToList()
                .ForEach(d => d.Delete(true));
            foreach (var item in Lis.Items)
            {
                if (item.InRoot || string.IsNullOrEmpty(item.UID))
                    continue;
                if (!Directory.Exists(Path.Combine(LoadDir, item.UID)))
                    Directory.CreateDirectory(Path.Combine(LoadDir, item.UID));
                if (!string.IsNullOrEmpty(item.File) && File.Exists(item.File))
                {
                    File.Copy(item.File, Path.Combine(LoadDir, item.UID, item.FileName), true);
                }
            }
        }

        public static void GenerateUID(FileItem item)
        {
            if (item == null)
                return;
            if (!File.Exists(item.File))
                return;
            item.FileName = Path.GetFileName(item.File);
            if (item.File.StartsWith(RootDir))
            {
                item.InRoot = true;
                item.UID = Path.GetDirectoryName(item.File).Substring(0, RootDir.Length);
                return;
            }
            else
            {
                item.InRoot = false;
                item.UID = SHA256File(File.OpenRead(item.File));
            }
        }

        public static FileStream GetFileStream(string sha256)
        {
            var fn = GetFile(sha256);
            if (fn != null && File.Exists(fn))
            {
                return File.OpenRead(fn);
            }
            return null;
        }

        public static string GetFile(string sha256)
        {
            if (Lis == null)
                return null;
            if (Lis.Items.Exists(d => d.UID == sha256))
            {
                var item = Lis.Items.Find(d => d.UID == sha256);
                var fn = Path.Combine(item.UID, item.FileName);
                if (item.InRoot)
                {
                    fn = Path.Combine(RootDir, fn);
                }
                else
                {
                    fn = Path.Combine(LoadDir, fn);
                }
                return fn;
            }
            return null;
        }

        public static void SetFileItem(string file)
        {
            Lis.Items.Add(new FileItem(file));
        }

        public static void SetAllFileItems(List<string> files)
        {
            Lis.Items.Clear();
            files.ForEach(f => Lis.Items.Add(new FileItem(f)));
        }

        public static void ClearFileItem()
        {
            Lis.Items.Clear();
        }
    }

    [XmlRoot]
    public class FileList
    {
        public FileList()
        {
            Items = new List<FileItem>();
        }

        [XmlElement("File")]
        public List<FileItem> Items { get; set; }
    }

    public class FileItem
    {
        public FileItem()
        {
        }

        public FileItem(string file)
        {
            File = file;
        }

        [XmlAttribute]
        public string FileName { get; set; }

        [XmlAttribute]
        public string UID { get; set; }

        [XmlIgnore]
        public string File { get; set; }

        [XmlAttribute]
        public bool InRoot { get; set; }
    }
}
